package kuti.grp5;

import java.awt.geom.*;
import java.util.*;
import kuti.*;

/**
 * The CollisionManager class manage the collision. It contains all information
 * about the collisions.
 *
 * @author Marc Schorderet <marc.schorderet@edu.hefr.ch>
 *
 */
public class CollisionManager {

    /**
     * The current kuti
     */
    private Kuti kuti;
    /**
     * last collision tick
     */
    private long lastCollisionTick;
    /**
     * last external collision step number
     */
    private long externalCollsion;
    /**
     * number of collision on this tick
     */
    private byte numCol;
    /**
     * setted true when a magnetic collision arrives.
     */
    private boolean magneticCollision = false;

    /**
     * The constructor create the collision manager
     *
     * @param kuti The current kuti
     *
     */
    public CollisionManager(Kuti kuti) {

        // Check if the kuti is valid
        if (kuti == null) {
            throw new IllegalArgumentException("The kuti is not defined");
        }

        this.kuti = kuti;

        this.lastCollisionTick = -1;

    }

    /**
     * This method check if the collision is the first collision
     *
     * @param stepNr The current stepNr
     *
     * @return Returns true, if it is the first collision, else returns false.
     *
     */
    public boolean isFirstCollision(long stepNr) {
        return stepNr != lastCollisionTick;
    }

    /**
     * This method check if an external collision is occured.
     *
     * @param stepNr The current step number
     *
     * @return Returns true if an external collision is occured, else returns
     * false.
     *
     */
    public boolean isExternalCollision(long stepNr) {
        return stepNr == externalCollsion;
    }

    /**
     * This method set an external collision.
     *
     * @param stepNr The current step number
     *
     * (An external collsion is an collision when the kuti not move. An other
     * kuti is colliding with this kuti)
     *
     */
    public void setExternalCollision(long stepNr) {

        if (lastCollisionTick == stepNr) {
            numCol++;
        } else {
            lastCollisionTick = stepNr;
            numCol = 1;
        }

        externalCollsion = stepNr;

    }

    /**
     * The method do all actions, we need to do, if the kuti colliding with an
     * obstacle.
     *
     * @param o The colliding obstacle
     *
     */
    private void collidingWithObstacle(IObstacle o) {
        // Nothing is defined for the obstacle
    }

    /**
     * The method do all actions, we need to do, if the kuti colliding with an
     * magnetic obstacle.
     *
     * @param mo The colliding magnetic obstacle
     *
     */
    private void collidingWithMagneticObstacle(IMagneticObstacle mo) {
        magneticCollision = true;
    }

    /**
     * The method do all actions, we need to do, if the kuti colliding with an
     * other kuti.
     *
     * @param kuti The other kuti
     * @param stepNr The current step number
     *
     * Do not exchange messages if it is not the first collision on this tick.
     *
     */
    private void collidingWithKuti(IKuti other, long stepNr) {

        Kuti k = (Kuti) other;

        // Check if this and the other kuti are active
        if (!k.isActive() || !kuti.isActive()) {
            return;
        }
        // Check if the kuti are on a charging station
        boolean isOnChargingA = false;
        boolean isOnChargingB = false;
        for (IChargingStation cs : k.getWorldManager().getAllChargingStation()) {
            if (((ChargingStation) cs).isKutiOnChargingStation(kuti, stepNr)) {
                isOnChargingA = true;
            }
            if (((ChargingStation) cs).isKutiOnChargingStation(k, stepNr)) {
                isOnChargingB = true;
            }
        }


        //check if the state is STOPPED (which means recharging)
        if (!(isOnChargingA && kuti.getState() == EKutiState.STOPPED) || !(isOnChargingB && k.getState() == EKutiState.STOPPED)) {
            balancingEnergy(k);

        }

        boolean isFirstCollsion = isFirstCollision(stepNr) && k.getCollisionManager().isFirstCollision(stepNr);

        if (isFirstCollsion) {
            // ExchangeMessage
            exchangeMessage(k);
        }


        // Modify the CollisionManager of the other kuti, to inform it, that we colliding with it.
        k.getCollisionManager().setExternalCollision(stepNr);

    }

    /**
     * This method ballencing the energy between two kuti.
     *
     * @param k The other kuti.
     *
     */
    private void balancingEnergy(Kuti k) {

        // Check if the other kuti is valid
        if (k == null) {
            throw new IllegalArgumentException("The kuti is not defined.");
        }

        // Calculate the mean energy
        double mean = (kuti.getEnergyLevel() + k.getEnergyLevel()) / 2;

        // Set the mean energy to the kuti
        k.getEnergyManager().equilibrate(mean);
        kuti.getEnergyManager().equilibrate(mean);

    }

    /**
     * This method exchange the message between two kuti
     *
     * @param k The other kuti
     *
     */
    private void exchangeMessage(Kuti k) {

        // Check if the other kuti is valid
        if (k == null) {
            throw new IllegalArgumentException("The kuti is not defined.");
        }

        // Get the messages
        char[] msg1 = k.getActuator().getCollisionMessage();
        char[] msg2 = kuti.getActuator().getCollisionMessage();

        // Set the message to the other kuti
        k.getActuator().setCollisionMessage(msg2);
        k.getActuator().setCollisionMessage(msg1);

    }

    /**
     * This method calculate the collision point between two kuti.
     *
     * @param other The other kuti
     *
     * @return Returns the collision point of the collision between two kuti.
     *
     */
    private Point2D getCollisionPoint(IKuti other) {

        // Check if the other kuti is valid
        if (other == null) {
            throw new IllegalArgumentException("The kuti is not defined.");
        }

        // Create kuti1 and kuti2 vectors
        Vector2D kuti1 = new Vector2D(kuti.getCenterPosition());
        Vector2D kuti2 = new Vector2D(other.getCenterPosition());

        // Create a vector from kuti1 to kuti2
        Vector2D collision = kuti2.clone();
        collision.sub(kuti1);

        // Divide the length by 2
        collision.stretch(0.5);

        // Add the vector kuti1
        collision.add(kuti1);

        return collision.getPoint();

    }

    /**
     * This method calculate the collision point between a kuti and an obstacle.
     *
     * @param obstacle The obstacle
     *
     * @return Returns the collision point of a collision between a kuti and an
     * obstacle.
     *
     */
    private Point2D getCollisionPoint(Line2D obstacle) {

        // Check if the other kuti is valid
        if (obstacle == null || obstacle.getP1() == null || obstacle.getP2() == null) {
            throw new IllegalArgumentException("The obstacle is not defined.");
        }

        // Create the obstacle vector
        Vector2D obststacleV = new Vector2D(obstacle.getP1(), obstacle.getP2());

        // Create the kuti vector
        Vector2D kutiV = new Vector2D(obstacle.getP1(), kuti.getCenterPosition());

        // Create a projection from the kuti to the obstalce
        Vector2D collision = Vector2D.projection(obststacleV, kutiV);

        // Add the obstacle vector
        collision.add(new Vector2D(obstacle.getP1()));

        return collision.getPoint();

    }

    /**
     * This method create a collsion in all world observer.
     *
     * @param element   The element where the collision is occured.
     * @param colPt     The collision point
     * @param observer  The list of the world observer.
     * @param stepNr    The current step number
     *
     */
    public void setCollisionPoint(IWorldElement element, Point2D colPt, List<IWorldObserver> observer, long stepNr) {

        // Check if the element is valid
        if (element == null) {
            throw new IllegalArgumentException("The world element is not defined");
        }

        // check if the world observer list is valid
        if (observer == null) {
            throw new IllegalArgumentException("The world observer list is not defined");
        }

        // check if the elements of the observer list are valid
        if (observer.contains(null)) {
            throw new IllegalArgumentException("The world observer list elements are not valid."
                    + "It contains the null value.");
        }

        // check the collision point
        if (colPt == null) {
            throw new IllegalArgumentException("The collision point is not define.");
        }

        // Check if the current step number is valid
        if (stepNr < 0) {
            throw new IllegalArgumentException("The step number is not valid. It must positif. [current value : " + stepNr + "]");
        }


        if (element.getElementType() == EWorldElementType.KUTI) {

            // do all other actions for an collision between two kuti
            collidingWithKuti((IKuti) element, stepNr);

        } else if (element.getElementType() == EWorldElementType.MAGNETIC_OBSTACLE) {

            IMagneticObstacle obstacle = (IMagneticObstacle) element;

            // do all other actions for an collision between a kuti and an mag obst.
            collidingWithMagneticObstacle(obstacle);

        } else if (element.getElementType() == EWorldElementType.OBSTACLE) {

            IObstacle obstacle = (IObstacle) element;

            // do all other actions for an collision between a kuti and an obstacle
            collidingWithObstacle(obstacle);

        } else {
            throw new IllegalArgumentException("The element " + element.getElementType() + " is not supported.");
        }

        // Update all world observer
        for (IWorldObserver e : observer) {
            e.collisionOccured(kuti, element, colPt);
        }

        if (lastCollisionTick == stepNr) {

            // Update number of collision on this tick
            numCol++;

        } else {

            // Update last collision step number
            lastCollisionTick = stepNr;

            //reset number of collision on this tick
            numCol = 0;
        }

    }
    
    /**
     * Check if it is a collision with an magnetic obstacle.
     * 
     * @return Returns true if the collision is with a magnetic obstacle, else
     * returns false.
     * 
     */

    public boolean getMagneticCollision() {
        return magneticCollision;
    }
    
    /**
     * Set a magnetic collision.
     * 
     * @param magneticCollision     True if the collision is with an magnetic
     * obstacle, else false.
     * 
     */

    public void setMagneticCollision(boolean magneticCollision) {
        this.magneticCollision = magneticCollision;
    }
}
